﻿using System;
using System.Collections.Generic;
using System.Linq;
using Borg;
using Borg.AddOn;
using Borg.Properties;

public sealed class GarbageCollector : RuntimeElement
{
    private bool _autoCollect;
    private bool _isCollecting;

    private readonly IList<Extension> _toBeInspected;

    private IGarbageCollectorApiHook _apiHook;

    public bool AutoCollect
    {
        get { return _autoCollect; }
        set
        {
            Composer.SetProperty(
                this,
                PublicSetAutoCollect,
                value,
                Messages.GarbageCollector_ErrorOnSettingGarbageCollectorProperty,
                "AutoCollect");
        }
    }

    internal GarbageCollector(Runtime runtime)
        : base(runtime)
    {
        _autoCollect = true;
        _isCollecting = false;

        _toBeInspected = new List<Extension>();
    }

    private void PublicSetAutoCollect(bool value)
    {
        _apiHook.OnSetProperty(
            value
                ? GarbageCollectorOperation.EnableCollect
                : GarbageCollectorOperation.DisableCollect);
        
        _autoCollect = value;
    }

    internal void Init(IGarbageCollectorApiHook apiHook)
    {
        _apiHook = apiHook;
    }

    public void ScheduleInspectionOf(Extension extension)
    {
        if (extension == null)
            throw new ArgumentNullException("extension");

        if (!CheckAccess())
            throw GetAccessException();

        InternalScheduleInspectionOf(new[] {extension});
    }

    internal void InternalScheduleInspectionOf(params Extension[] extensions)
    {
        var inspected = !_isCollecting && _toBeInspected.Count == 0;

        foreach (var extension in extensions)
            _toBeInspected.Add(extension);

        if (!inspected)
            return;

        Dispatcher.BeginInvoke(new Action(TryAutoCollect), new object[0]);
    }

    private void TryAutoCollect()
    {
        if (Runtime.State != RuntimeState.Running || !AutoCollect)
            return;

        if (Dispatcher.IsIdle())
            InternalCollect();
        else
            Dispatcher.BeginInvoke(new Action(TryAutoCollect), new object[0]);
    }

    internal void InternalCollect()
    {
        _isCollecting = true;

        while (_toBeInspected.Count > 0)
        {
            ISet<Extension> set = new HashSet<Extension>(_toBeInspected);
            _toBeInspected.Clear();

            foreach (var extension in set.Where(
                extension => !Composer.HasPluggedPlugs(extension) &&
                    !Composer.InternalIsDestroying(extension) &&
                    !InstanceStore.InternalIsDestroyed(extension)))
            {
                if (Composer.HasTaggedPlugs(extension))
                {
                    if (InstanceStore.InternalIsActivated(extension) &&
                        !Composer.InternalIsDeactivating(extension) &&
                        extension.ExtensionType.ActivationMode < ActivationMode.OnCreate)
                        Composer.InternalDeactivate(extension);
                }
                else
                    Composer.InternalDestroy(extension);
            }
        }
        
        _isCollecting = false;
    }
}
